﻿using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using CommunityToolkit.Mvvm.Messaging;
using MoreLinq;
using Serilog;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Models.MailItem;
using Wino.Extensions;
using Wino.Mail.ViewModels.Data;
using Wino.Mail.ViewModels.Messages;

namespace Wino.Controls.Advanced;

/// <summary>
/// Custom ListView control that handles multiple selection with Extended/Multiple selection mode
/// and supports threads.
/// </summary>
public partial class WinoListView : ListView, IDisposable
{
    private ILogger logger = Log.ForContext<WinoListView>();

    private const string PART_ScrollViewer = "ScrollViewer";
    private ScrollViewer internalScrollviewer;

    /// <summary>
    /// Gets or sets whether this ListView belongs to thread items.
    /// This is important for detecting selected items etc.
    /// </summary>
    public bool IsThreadListView
    {
        get { return (bool)GetValue(IsThreadListViewProperty); }
        set { SetValue(IsThreadListViewProperty, value); }
    }

    public ICommand ItemDeletedCommand
    {
        get { return (ICommand)GetValue(ItemDeletedCommandProperty); }
        set { SetValue(ItemDeletedCommandProperty, value); }
    }

    public ICommand LoadMoreCommand
    {
        get { return (ICommand)GetValue(LoadMoreCommandProperty); }
        set { SetValue(LoadMoreCommandProperty, value); }
    }

    public bool IsThreadScrollingEnabled
    {
        get { return (bool)GetValue(IsThreadScrollingEnabledProperty); }
        set { SetValue(IsThreadScrollingEnabledProperty, value); }
    }

    public static readonly DependencyProperty IsThreadScrollingEnabledProperty = DependencyProperty.Register(nameof(IsThreadScrollingEnabled), typeof(bool), typeof(WinoListView), new PropertyMetadata(false));
    public static readonly DependencyProperty LoadMoreCommandProperty = DependencyProperty.Register(nameof(LoadMoreCommand), typeof(ICommand), typeof(WinoListView), new PropertyMetadata(null));
    public static readonly DependencyProperty IsThreadListViewProperty = DependencyProperty.Register(nameof(IsThreadListView), typeof(bool), typeof(WinoListView), new PropertyMetadata(false, new PropertyChangedCallback(OnIsThreadViewChanged)));
    public static readonly DependencyProperty ItemDeletedCommandProperty = DependencyProperty.Register(nameof(ItemDeletedCommand), typeof(ICommand), typeof(WinoListView), new PropertyMetadata(null));

    public WinoListView()
    {
        CanDragItems = true;
        IsItemClickEnabled = true;
        IsMultiSelectCheckBoxEnabled = true;
        IsRightTapEnabled = true;
        SelectionMode = ListViewSelectionMode.Extended;
        ShowsScrollingPlaceholders = false;
        SingleSelectionFollowsFocus = true;

        DragItemsCompleted += ItemDragCompleted;
        DragItemsStarting += ItemDragStarting;
        SelectionChanged += SelectedItemsChanged;
        ProcessKeyboardAccelerators += ProcessDelKey;
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        internalScrollviewer = GetTemplateChild(PART_ScrollViewer) as ScrollViewer;

        if (internalScrollviewer == null)
        {
            logger.Warning("WinoListView does not have an internal ScrollViewer. Infinite scrolling behavior might be effected.");
            return;
        }

        internalScrollviewer.ViewChanged -= InternalScrollVeiwerViewChanged;
        internalScrollviewer.ViewChanged += InternalScrollVeiwerViewChanged;
    }

    private static void OnIsThreadViewChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        if (obj is WinoListView winoListView)
        {
            winoListView.AdjustThreadViewContainerVisuals();
        }
    }

    private void AdjustThreadViewContainerVisuals()
    {
        if (IsThreadListView)
        {
            ItemContainerTransitions.Clear();
        }
    }

    private double lastestRaisedOffset = 0;
    private int lastItemSize = 0;

    // TODO: This is buggy. Does not work all the time. Debug.

    private void InternalScrollVeiwerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
    {
        if (internalScrollviewer == null) return;

        // No need to raise init request if there are no items in the list.
        if (Items.Count == 0) return;

        // If the scrolling is finished, check the current viewport height.
        if (e.IsIntermediate)
        {
            var currentOffset = internalScrollviewer.VerticalOffset;
            var maxOffset = internalScrollviewer.ScrollableHeight;

            if (currentOffset + 10 >= maxOffset && lastestRaisedOffset != maxOffset && Items.Count != lastItemSize)
            {
                // We must load more.
                lastestRaisedOffset = maxOffset;
                lastItemSize = Items.Count;

                LoadMoreCommand?.Execute(null);
            }
        }
    }

    private void ProcessDelKey(UIElement sender, Windows.UI.Xaml.Input.ProcessKeyboardAcceleratorEventArgs args)
    {
        if (args.Key == Windows.System.VirtualKey.Delete)
        {
            args.Handled = true;

            ItemDeletedCommand?.Execute(MailOperation.SoftDelete);
        }
    }

    private void ItemDragCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
    {
        if (args.Items.Any(a => a is MailItemViewModel))
        {
            args.Items.Cast<MailItemViewModel>().ForEach(a => a.IsCustomFocused = false);
        }
    }

    private void ItemDragStarting(object sender, DragItemsStartingEventArgs args)
    {
        // Dragging multiple mails from different accounts/folders are supported with the condition below:
        // All mails belongs to the drag will be matched on the dropped folder's account.
        // Meaning that if users drag 1 mail from Account A/Inbox and 1 mail from Account B/Inbox,
        // and drop to Account A/Inbox, the mail from Account B/Inbox will NOT be moved.

        if (IsThreadListView)
        {
            var allItems = args.Items.Cast<MailItemViewModel>();

            // Highlight all items
            allItems.ForEach(a => a.IsCustomFocused = true);

            // Set native drag arg properties.

            var dragPackage = new MailDragPackage(allItems.Cast<IMailItem>());

            args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
        }
        else
        {
            var dragPackage = new MailDragPackage(args.Items.Cast<IMailItem>());

            args.Data.Properties.Add(nameof(MailDragPackage), dragPackage);
        }
    }

    public void ChangeSelectionMode(ListViewSelectionMode selectionMode)
    {
        SelectionMode = selectionMode;

        if (!IsThreadListView)
        {
            Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
            {
                var threadListView = GetThreadInternalListView(c);

                if (threadListView != null)
                {
                    threadListView.SelectionMode = selectionMode;
                }
            });
        }
    }

    /// <summary>
    /// Finds the container for given mail item and adds it to selected items.
    /// </summary>
    /// <param name="mailItemViewModel">Mail to be added to selected items.</param>
    /// <returns>Whether selection was successful or not.</returns>
    public bool SelectMailItemContainer(MailItemViewModel mailItemViewModel)
    {
        var itemContainer = ContainerFromItem(mailItemViewModel);

        // This item might be in thread container.
        if (itemContainer == null)
        {
            bool found = false;

            Items.OfType<ThreadMailItemViewModel>().ForEach(c =>
            {
                if (!found)
                {
                    var threadListView = GetThreadInternalListView(c);

                    if (threadListView != null)
                        found = threadListView.SelectMailItemContainer(mailItemViewModel);
                }
            });

            return found;
        }

        SelectedItems.Add(mailItemViewModel);
        return true;
    }

    /// <summary>
    /// Recursively clears all selections except the given mail.
    /// </summary>
    /// <param name="exceptViewModel">Exceptional mail item to be not unselected.</param>
    /// <param name="preserveThreadExpanding">Whether expansion states of thread containers should stay as it is or not.</param>
    public void ClearSelections(MailItemViewModel exceptViewModel = null, bool preserveThreadExpanding = false)
    {
        SelectedItems.Clear();

        Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
        {
            var threadListView = GetThreadInternalListView(c);

            if (threadListView == null)
                return;

            if (exceptViewModel != null)
            {
                if (!threadListView.SelectedItems.Contains(exceptViewModel))
                {
                    if (!preserveThreadExpanding)
                    {
                        c.IsThreadExpanded = false;
                    }

                    threadListView.SelectedItems.Clear();
                }
            }
            else
            {
                if (!preserveThreadExpanding)
                {
                    c.IsThreadExpanded = false;
                }

                threadListView.SelectedItems.Clear();
            }
        });
    }

    /// <summary>
    /// Recursively selects all mails, including thread items.
    /// </summary>
    public void SelectAllWino()
    {
        SelectAll();

        Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
        {
            c.IsThreadExpanded = true;

            var threadListView = GetThreadInternalListView(c);

            threadListView?.SelectAll();
        });
    }

    // SelectedItems changed.
    private void SelectedItemsChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.RemovedItems != null)
        {
            foreach (var removedItem in e.RemovedItems)
            {
                if (removedItem is MailItemViewModel removedMailItemViewModel)
                {
                    // Mail item un-selected.

                    removedMailItemViewModel.IsSelected = false;
                    WeakReferenceMessenger.Default.Send(new MailItemSelectionRemovedEvent(removedMailItemViewModel));
                }
                else if (removedItem is ThreadMailItemViewModel removedThreadItemViewModel)
                {
                    removedThreadItemViewModel.IsThreadExpanded = false;
                }
            }
        }

        if (e.AddedItems != null)
        {
            foreach (var addedItem in e.AddedItems)
            {
                if (addedItem is MailItemViewModel addedMailItemViewModel)
                {
                    // Mail item selected.

                    addedMailItemViewModel.IsSelected = true;

                    WeakReferenceMessenger.Default.Send(new MailItemSelectedEvent(addedMailItemViewModel));
                }
                else if (addedItem is ThreadMailItemViewModel threadMailItemViewModel)
                {
                    if (IsThreadScrollingEnabled)
                    {
                        if (internalScrollviewer != null && ContainerFromItem(threadMailItemViewModel) is FrameworkElement threadFrameworkElement)
                        {
                            internalScrollviewer.ScrollToElement(threadFrameworkElement, true, true, bringToTopOrLeft: true);
                        }
                    }

                    // Try to select first item.
                    if (GetThreadInternalListView(threadMailItemViewModel) is WinoListView internalListView)
                    {
                        internalListView.SelectFirstItem();
                    }
                }
            }
        }

        if (!IsThreadListView)
        {
            if (SelectionMode == ListViewSelectionMode.Extended && SelectedItems.Count == 1)
            {
                // Only 1 single item is selected in extended mode for main list view.
                // We should un-select all thread items.

                Items.Where(a => a is ThreadMailItemViewModel).Cast<ThreadMailItemViewModel>().ForEach(c =>
                {
                    // c.IsThreadExpanded = false;

                    var threadListView = GetThreadInternalListView(c);

                    threadListView?.SelectedItems.Clear();
                });
            }
        }
    }

    public async void SelectFirstItem()
    {
        if (Items.Count > 0)
        {
            if (Items[0] is MailItemViewModel firstMailItemViewModel)
            {
                // Make sure the invisible container is realized.
                await Task.Delay(250);

                if (ContainerFromItem(firstMailItemViewModel) is ListViewItem firstItemContainer)
                {
                    firstItemContainer.IsSelected = true;
                }

                firstMailItemViewModel.IsSelected = true;
            }
        }
    }

    private WinoListView GetThreadInternalListView(ThreadMailItemViewModel threadMailItemViewModel)
    {
        var itemContainer = ContainerFromItem(threadMailItemViewModel);

        if (itemContainer is ListViewItem listItem)
        {
            var expander = listItem.GetChildByName<WinoExpander>("ThreadExpander");

            if (expander != null)
                return expander.Content as WinoListView;
        }

        return null;
    }

    public void Dispose()
    {
        DragItemsCompleted -= ItemDragCompleted;
        DragItemsStarting -= ItemDragStarting;
        SelectionChanged -= SelectedItemsChanged;
        ProcessKeyboardAccelerators -= ProcessDelKey;

        if (internalScrollviewer != null)
        {
            internalScrollviewer.ViewChanged -= InternalScrollVeiwerViewChanged;
        }

        foreach (var item in Items)
        {
            if (item is ThreadMailItemViewModel threadMailItemViewModel)
            {
                var threadListView = GetThreadInternalListView(threadMailItemViewModel);
                threadListView?.Dispose();
            }
        }
    }
}
